package com.shinbada.core.security.shiro;

import com.google.common.collect.Maps;
import com.shinbada.common.Common;
import com.shinbada.core.security.shiro.annotation.OpenApi;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.AnnotatedElement;
import java.util.Map;

/**
 * @author initcap
 * @date Created in 2021/1/24 14:47.
 * @see OpenApi 动态扫描 @OpenApi注解，并注册到shiro过滤器中
 */
@Data
@Slf4j
@Component
public class SpringOpenApiProcessor implements BeanPostProcessor {

    @Autowired
    private ShiroFilterFactoryBean shiroFilter;

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 这里必须使用ClassUtils.getUserClass(bean)，而不能bean.getClass()，否则会有的Controller找不到 为啥???
        Class<?> clz = ClassUtils.getUserClass(bean);
        boolean hasController = clz.isAnnotationPresent(Controller.class);
        boolean hasRestController = clz.isAnnotationPresent(RestController.class);
        if (hasController || hasRestController) {
            ReflectionUtils.doWithMethods(clz, method -> {
                AnnotationAttributes openApis = AnnotatedElementUtils.getMergedAnnotationAttributes(method, OpenApi.class);
                if (CollectionUtils.isEmpty(openApis)) {
                    return;
                }
                // 获取Controller上的注解信息
                String parentPath = getRequestMappingPath(clz);
                // 获取方法上同时具备@OpenApi + @RequestMapping的注解(@PutMapping等都继承@RequestMapping)
                String subPath = getRequestMappingPath(method);
                // 解析出shiro匹配路径
                String shiroPath = buildPath(parentPath, subPath, openApis.getString("postfix"));
                // 这里切记不能直接操作 shiroFilter.getFilterChainDefinitionMap()
                updatePermission(shiroPath);
                log.info("【OpenApi】扫描到注解，添加shiro过滤地址 ->{path:{}}", shiroPath);
            });
        }
        return bean;
    }

    /**
     * 获取@RequestMapping注解上设置的value信息
     *
     * @param element 可以是类 和 方法
     * @return value信息
     */
    private String getRequestMappingPath(AnnotatedElement element) {
        AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(element, RequestMapping.class);
        return CollectionUtils.isEmpty(attributes) ? "" : StringUtils.defaultString(attributes.getStringArray("value")[0]);
    }

    /**
     * @param parent  Controller类上 @RequestMapping 解析出来的路径
     * @param sub     Controller方法上 @RequestMapping 解析出来的路径
     * @param postfix 额外增加的路径
     * @return 拼装好的路径
     */
    private String buildPath(String parent, String sub, String postfix) {
        // 如果都是空的，整了个寂寞呀?
        if (StringUtils.isBlank(parent) && StringUtils.isBlank(sub)) {
            return "";
        }
        if (!parent.endsWith("/") && !sub.startsWith("/")) {
            parent += "/";
        }
        // TODO: 这里应该将 /api/rest/{openid}/abc 替换成 /api/rest/*/abc
        return parent.concat(sub).concat(postfix);
    }

    /**
     * 在对过滤器链进行增删改操作时，需要调用此方法进行动态刷新
     */
    private void updatePermission(String path) {
        Map<String, String> definitionMap = Maps.newLinkedHashMap(shiroFilter.getFilterChainDefinitionMap());
        definitionMap.put(path, Common.Shiro.ANON);
        // 启动的时候加上整体拦截器，所有请求通过我们自己的JWT Filter
        definitionMap.remove("/**");
        definitionMap.put("/**", "jwt,kickout");

        AbstractShiroFilter abstractShiroFilter;
        try {
            abstractShiroFilter = (AbstractShiroFilter) shiroFilter.getObject();
        } catch (Exception e) {
            log.error("get ShiroFilter from shiroFilterFactoryBean error!");
            throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
        }
        PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) abstractShiroFilter.getFilterChainResolver();
        DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();

        // 清空老的权限控制
        manager.getFilterChains().clear();

        shiroFilter.getFilterChainDefinitionMap().clear();
        shiroFilter.setFilterChainDefinitionMap(definitionMap);
        // 重新构建生成
        Map<String, String> chains = shiroFilter.getFilterChainDefinitionMap();
        for (Map.Entry<String, String> entry : chains.entrySet()) {
            String url = entry.getKey();
            String chainDefinition = entry.getValue().trim().replace(" ", "");
            manager.createChain(url, chainDefinition);
        }

    }

}
